package com.ailanyin.modules.system.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.ailanyin.common.base.constants.CommonConstant;
import com.ailanyin.common.base.exception.Assert;
import com.ailanyin.common.base.result.AjaxResult;
import com.ailanyin.common.redis.service.RedisService;
import com.ailanyin.common.security.constants.SecurityConstant;
import com.ailanyin.common.security.utils.SecurityUtil;
import com.ailanyin.common.security.utils.TokenUtil;
import com.ailanyin.modules.system.domain.SysBlacklistUser;
import com.ailanyin.modules.system.domain.SysLoginLog;
import com.ailanyin.modules.system.domain.SysUser;
import com.ailanyin.modules.system.domain.vo.LoginBodyVo;
import com.ailanyin.modules.system.mapper.SysUserMapper;
import com.ailanyin.modules.system.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * @author ailanyin
 * @version 1.0
 * @since 2022/9/19 0019 下午 20:01
 */
@Service
public class SysLoginServiceImpl implements ISysLoginService {

    @Autowired
    private HttpServletRequest request;
    @Autowired
    private RedisService redisService;
    @Autowired
    private ISysRoleService sysRoleService;
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private ISysLoginLogService sysLoginLogService;
    @Autowired
    private SysBlacklistUserService sysBlacklistUserService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Resource
    private SysUserMapper userMapper;

    @Override
    public AjaxResult login(String username, String password, String code, String uuid) {
        String ip = ServletUtil.getClientIP(request);
        String userAgentStr = request.getHeader("User-Agent");
        //黑名单开关
        boolean checkBlacklistOnOffResult = checkBlacklistOnOff();
        if (checkBlacklistOnOffResult) {
            //查看登录ip是否被拉黑
            checkBlacklist(ip);
        }
        //验证码
        checkCaptcha(code, uuid);
        //用户名查询用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        //验证密码
        boolean checkPasswordResult = SecurityUtil.matchesPassword(password, userDetails.getPassword());
        //密码错误
        if (!checkPasswordResult) {
            ThreadUtil.execAsync(() -> {
                addLoginLog(username, ip, CommonConstant.FAIL, SecurityConstant.USERNAME_OR_PASSWORD_ERROR, userAgentStr);
            });
            //黑名单
            if (checkBlacklistOnOffResult) {
                return AjaxResult.error(handleBlacklist(ip));
            }
            return AjaxResult.error(SecurityConstant.USERNAME_OR_PASSWORD_ERROR);
        }
        //密码正确 删除错误记录
        ThreadUtil.execAsync(() -> {
            deleteBlacklistUserFromRedis(ip);
            // update this user last login ip and time
            updateUserLastLoginIpAndTime(userDetails.getUsername(), ip);
            // add login log
            addLoginLog(username, ip, CommonConstant.SUCCESS, SecurityConstant.LOGIN_SUCCESS, userAgentStr);
        });
        AjaxResult ajax = AjaxResult.success();
        ajax.put("token" , TokenUtil.generateToken(userDetails.getUsername()));
        return ajax;
    }

    @Override
    public AjaxResult getCaptchaImage() {
        AjaxResult ajax = AjaxResult.success();

        if (!getRegisterConfig()) {
            ajax.put("registerUser" , false);
        }
        if (!getCaptchaConfig()) {
            ajax.put("captchaOnOff" , false);
            return ajax;
        }
        String uuid = UUID.randomUUID().toString(true);
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(150, 50, 4, 20);
        ajax.put("uuid" , uuid);
        ajax.put("img" , captcha.getImageBase64());
        redisService.set(SecurityConstant.CAPTCHA_KEY + uuid, captcha.getCode(), 120);

        return ajax;
    }

    @Override
    public void register(LoginBodyVo register) {
        // 检查是否开启注册
        if (!getRegisterConfig()) {
            Assert.fail("系统暂未开启注册！");
        }
        // 检查验证码
        checkCaptcha(register.getCode(), register.getUuid());
        // 新建用户
        SysUser user = new SysUser();
        user.setUserName(register.getUsername());
        user.setNickName(register.getUsername());
        user.setPassword(register.getPassword());
        sysUserService.insertUser(user);
    }

    @Override
    public AjaxResult getInfo() {
        AjaxResult ajax = AjaxResult.success();
        String username = SecurityUtil.getLoginUsername();
        SysUser user = sysUserService.getUserFromCache(username);
        // 权限集合
        Set<String> perms = new HashSet<>();
        // 角色集合
        Set<String> roles = new HashSet<>();
        user.getRoles().forEach(sysRole -> {
            perms.addAll(sysRoleService.getRolePermsFromCache(sysRole.getRoleId()));
            roles.add(sysRole.getRoleKey());
        });

        ajax.put("permissions" , perms);
        ajax.put("user" , user);
        ajax.put("roles" , roles);
        return ajax;
    }

    private String handleBlacklist(String ip) {
        int errorCount;
        StringBuilder builder = new StringBuilder();
        try {
            //半小时过期时间
            errorCount = (int) redisService.hGet(SecurityConstant.LOGIN_ERROR_COUNT, ip);
            ++errorCount;
            if (errorCount < SecurityConstant.MAX_ERROR_COUNT) {
                redisService.hSet(SecurityConstant.LOGIN_ERROR_COUNT, ip, errorCount, 1800);
                builder.append("您还有")
                        .append(SecurityConstant.MAX_ERROR_COUNT - errorCount)
                        .append("次输错密码的机会！");
                return builder.toString();
            } else {
                // 黑名单进Redis
                redisService.sAdd(SecurityConstant.BLACKLIST_KEY, ip);
                // 黑名单进Mysql
                addBlacklistUser(ip);
                return SecurityConstant.HAS_BEEN_PULLED_BLACK;
            }

        } catch (Exception e) {
            redisService.hSet(SecurityConstant.LOGIN_ERROR_COUNT, ip, 1, 1800);
            builder.append("您还有")
                    .append(SecurityConstant.MAX_ERROR_COUNT - 1)
                    .append("次输错密码的机会！");
            return builder.toString();
        }
    }

    private void addBlacklistUser(String ip) {
        SysBlacklistUser blacklistUser = new SysBlacklistUser();
        blacklistUser.setIp(ip);
        // "1" == "错误密码次数过多"
        blacklistUser.setReason("1");
        sysBlacklistUserService.insertBlacklistUser(blacklistUser);
    }

    private void deleteBlacklistUserFromRedis(String ip) {
        redisService.hDel(SecurityConstant.LOGIN_ERROR_COUNT, ip);
    }

    private void updateUserLastLoginIpAndTime(String username, String ip) {
        userMapper.updateUserLastLoginIpAndTime(username, ip, new Date());
    }

    private boolean checkBlacklistOnOff() {
        try {
            return SecurityConstant.BLACKLIST_ON.equals(redisService.get(SecurityConstant.BLACKLIST_ON_OFF_KEY));
        } catch (Exception e) {
            Assert.fail("Redis中黑名单配置不存在！");
            return false;
        }
    }

    private void checkBlacklist(String ip) {
        if (redisService.sIsMember(SecurityConstant.BLACKLIST_KEY, ip)) {
            Assert.fail("IP:[" + ip + "]已经被拉黑，请联系管理员！");
        }
    }

    private boolean getCaptchaConfig() {
        try {
            return SecurityConstant.CAPTCHA_ON.equals(redisService.get(SecurityConstant.CAPTCHA_ON_OFF_KEY));
        } catch (Exception e) {
            Assert.fail("Redis中验证码配置不存在！");
            return false;
        }
    }

    private boolean getRegisterConfig() {
        try {
            return SecurityConstant.REGISTER_ON.equals(redisService.get(SecurityConstant.REGISTER_ON_OFF_KEY));
        } catch (Exception e) {
            Assert.fail("Redis中注册配置不存在！");
            return false;
        }
    }

    private void addLoginLog(final String username, final String ip, final String status, final String message, String userAgentStr) {
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
        SysLoginLog loginLog = new SysLoginLog();
        loginLog.setUserName(username);
        loginLog.setIp(ip);
        //查IP所在地
        //loginLog.setLoginLocation(IpUtil.getRealAddressByIp(ip))
        loginLog.setBrowser(userAgent.getBrowser().toString());
        loginLog.setOs(userAgent.getOs().toString());
        loginLog.setMsg(message);
        loginLog.setStatus(status);
        sysLoginLogService.insertLoginLog(loginLog);
    }

    private void checkCaptcha(String code, String uuid) {
        if (getCaptchaConfig()) {
            if (StrUtil.isBlank(code) || StrUtil.isBlank(uuid)) {
                Assert.fail("验证码或uuid获取失败！");
            }
            try {
                if (!redisService.hasKey(SecurityConstant.CAPTCHA_KEY + uuid)) {
                    Assert.fail("验证码已经过期失效！");
                } else {
                    if (!code.equalsIgnoreCase(redisService.get(SecurityConstant.CAPTCHA_KEY + uuid).toString())) {
                        Assert.fail("验证码输入错误！");
                    }
                }

            } finally {
                redisService.del(SecurityConstant.CAPTCHA_KEY + uuid);
            }
        }
    }
}
